package network;

import jdk.internal.util.xml.impl.Input;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    // 构造一个 ServerSocket 对象
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService service = Executors.newCachedThreadPool();
        while(true){
            // 通过 accept 方法把内核中已经建立好的连接拿到应用程序中
            // 建立连接的细节流程都是内核自动完成的,应用程序只需要 ‘’捡现成‘’ 的
            Socket clientSocket = serverSocket.accept();
            // 通过这个方法来处理当前的连接
            // 此处不应该直接调用processConnection这个方法,会导致服务器不能处理多个客户端
            // 创建新的线程来调用是更合理的做法

            // 这种方法可行,但是不够好
//            Thread t = new Thread(()->{
//                processConnection(clientSocket);
//            });
//            t.start();

            // 更好一点的方法是使用线程池
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    // 通过这个方法来处理当前的连接
    private void processConnection(Socket clientSocket) {
        // 进入方法,打印一个日志,表示客户端有用户连上了
        System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        // 接下来进行数据的交互
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            // 使用 try() 的方式,避免后续用完了流对象,忘记close
            // 由于客户端发来的数据可能是多条数据,对于多条数据就要循环处理
            while(true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()) {
                    // 连接断开了,此时循环就要结束了
                    System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                // 1. 读取请求并解析,此次以next作为读取请求的方式,next的规则是读到空白符就返回
                String request = scanner.next();
                // 2. 根据请求,计算响应
                String response = process(request);
                // 3. 把响应写回到客户端
                //    可以把 String 转成字节数组,写入到 OutputString
                //    也可以使用 PrintWriter 把 OutputStream 包裹一下,来写入字符串
                PrintWriter printWriter = new PrintWriter(outputStream);
                // 此处的 println 不是打印到控制台了,而是写入到 outputStream 对应的流对象中,也就是写入到clientSocket里面
                // 这个数据也是通过网络发送出去了(发给当前连接这个的另外一端)
                // 这里使用 println 带有\n是为了后续客户端这边可以使用 scanner.next 来读取数据
                printWriter.println(response);
                // 这里还要刷新缓冲区,如果没有刷新操作,后续数据可能仍然在内存中,没有被写入网卡
                printWriter.flush();
                // 4. 打印一下这次请求交互过程中内容
                System.out.printf("[%s:%d] req=%s, res=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),
                        request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            // 在这个地方进行 clientSocket 的关闭
            // processConnection 就是在处理一个连接,在这个方法执行完毕,这个连接也就处理完了
            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public String process(String request){
        // 这是回显服务器,请求和响应都是一样的
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}
